package bancosys.tec.services.input.driver.xls;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.impl.ServicesImplMessages;
import jmine.tec.services.io.ServiceBean;
import jmine.tec.services.io.ServicesPageBean;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import bancosys.tec.datadigester.DigesterDriver;
import bancosys.tec.datadigester.DigesterDriverException;
import bancosys.tec.datadigester.DigesterFile;
import bancosys.tec.services.input.XLSFileConstants;
import bancosys.tec.services.input.driver.bean.ServicePageHeader;
import bancosys.tec.services.input.exceptions.InputException;
import bancosys.tec.services.input.exceptions.InvalidFormulaException;

/**
 * Driver para leitura de serviços escritos em planilha (xls).
 * 
 * @author Kendji
 * @author Rafael Volpato
 */
public class ServiceXLSDriver implements DigesterDriver<ServicePageHeader> {

    /**
     * Parseia o arquivo.
     * 
     * @param file file
     * @param props propriedades para parse dos drivers
     * @return servicePageHeader com os dados do arquivo
     */
    public ServicePageHeader parse(DigesterFile file, Map<String, String> props) {
        HSSFWorkbook workbook = null;
        int numberOfSheets = 0;

        try {
            workbook = new HSSFWorkbook(file.getInputStream());
            numberOfSheets = workbook.getNumberOfSheets();
        } catch (IOException e) {
            throw new DigesterDriverException(ServicesImplMessages.DRIVER_ERROR_READING_WORKBOOK.create(), e);
        }
        XLSUtils utils = new XLSUtils(workbook);
        ServicePageHeader header = new ServicePageHeader();
        header.setFileName(file.getFileName());
        for (int sheetPos = 0; sheetPos < numberOfSheets; sheetPos++) {
            HSSFSheet sheet = workbook.getSheetAt(sheetPos);
            String sheetName = workbook.getSheetName(sheetPos);
            if (sheetName.startsWith(XLSFileConstants.IGNORE_SHEET_PREFIX)) {
                continue;
            }
            ServicesPageBean page = this.doConvertSheet(utils, sheetName, sheet);
            header.addSheet(page);
        }

        return header;
    }

    /**
     * Converte uma sheet em um objeto do tipo ServicesPage.
     * 
     * @param utils xlsutils
     * @param sheetName nome da aba da planilha
     * @param sheet a aba da planilha
     * @return Uma instancia de um ServicesPage.
     */
    private ServicesPageBean doConvertSheet(XLSUtils utils, String sheetName, HSSFSheet sheet) {
        List<String> header = new ArrayList<String>();
        ServicesPageBean page = new ServicesPageBean();
        page.setName(sheetName);

        /*
         * Iterate over the rows of the sheet and convert these rows into services
         */
        for (int rowPos = sheet.getFirstRowNum(); rowPos <= sheet.getLastRowNum(); rowPos++) {
            HSSFRow row = sheet.getRow(rowPos);

            if (row == null) {
                header = Collections.emptyList();
                continue;
            }

            /*
             * Obter a primeira celula da linha: (1) - Se a celula estiver vazia, verificar se a terceira celula esta definindo o cabecalho
             * da tabela. (2) - Se a celula conter uma acao valida, criar um novo servico. (3) - Se a celula nao conter uma acao valida,
             * ignorar.
             */
            try {
                String cellContents = utils.getCellContentAsString(sheetName, row, XLSFileConstants.ACTIONS_COLUMN);
                String firstHeaderContents = utils.getCellContentAsString(sheetName, row, XLSFileConstants.RESERVED_COLUMNS);

                if (cellContents.trim().startsWith("#") || cellContents.matches("^(\\-|\\+)?\\d+((\\.)?\\d*)?$")) {
                    // Linha de comentário ou somente numeros na primeira coluna
                    continue;
                }

                if ("".equals(cellContents)) {
                    if (!"".equals(firstHeaderContents)) {
                        /*
                         * A linha corrente representa o header de uma tabela
                         */
                        header = this.doReadHeader(utils, sheetName, row);
                    } else {
                        /*
                         * A linha corrente não define um header portanto podemos limpar o header anterior
                         */
                        header = Collections.emptyList();
                    }
                } else if (ActionsEnum.isValid(cellContents)) {
                    /*
                     * A linha corrente representa uma acao a ser executada
                     */
                    ActionsEnum action = ActionsEnum.valueOf(cellContents);

                    if (action.getAllowedServicesCount() == -1 && header.isEmpty()) {
                        /*
                         * Cada coluna representa um servico a ser executado
                         */
                        for (int cellPos = XLSFileConstants.SERVICES_COLUMN; cellPos < row.getLastCellNum(); cellPos++) {
                            ServiceBean service = this.doInstantiateService(utils, sheetName, row, cellPos, action);
                            if (service != null) {
                                page.addService(service);
                            }
                        }
                    } else {
                        /*
                         * assumindo aqui que qualquer valor diferente de -1 para action.getAllowedServicesCount() é válido e significa um
                         * serviço por linha
                         */
                        ServiceBean service = this.doInstantiateService(utils, sheetName, row, XLSFileConstants.SERVICES_COLUMN, action);
                        if (service != null) {
                            this.doReadServiceProperties(utils, service, sheetName, row, action, header);
                            page.addService(service);
                        }
                    }

                } else {
                    if (!cellContents.trim().equals("") && cellContents.equals(cellContents.toUpperCase())) {
                        /*
                         * Ação inválida. Deve criar um serviço de erro.
                         */
                        ServiceBean service =
                                this.doInstantiateService(utils, sheetName, row, XLSFileConstants.SERVICES_COLUMN, ActionsEnum.INVALIDA);
                        if (service != null) {
                            this.doReadServiceProperties(utils, service, sheetName, row, ActionsEnum.INVALIDA, header);
                            service.setComment("Action: " + cellContents + " - Service name: " + service.getName());
                            page.addService(service);
                        }
                    }
                }
            } catch (InputException e) {
                throw new InvalidFormulaException(e);
            }
        }

        return page;
    }

    /**
     * Le o Header de uma tabela
     * 
     * @param utils xlsutils
     * @param sheetName nome da aba.
     * @param row a linha
     * @return lista de strings, com os dados da linha
     * @throws InputException caso ocorra algum erro
     */
    private List<String> doReadHeader(XLSUtils utils, String sheetName, HSSFRow row) throws InputException {
        List<String> header = new ArrayList<String>();

        /* Posicao LOGICA da celula na linha */
        for (int cellPos = XLSFileConstants.RESERVED_COLUMNS; cellPos < row.getLastCellNum(); cellPos++) {
            header.add(cellPos - XLSFileConstants.RESERVED_COLUMNS, utils.getCellContentAsString(sheetName, row, cellPos));
        }

        return header;
    }

    /**
     * Cria uma nova instancia do servico especificado na linha da planilha.
     * 
     * @param utils xlsutils
     * @param sheetName sheetName
     * @param row row
     * @param colIndex colIndex
     * @param action action
     * @return Uma nova instancia do servico espeficicado na linha
     * @throws InputException caso ocorra algum erro.
     */
    private ServiceBean doInstantiateService(XLSUtils utils, String sheetName, HSSFRow row, int colIndex, ActionsEnum action)
            throws InputException {
        ServiceBean service = null;

        String serviceName = utils.getCellContentAsString(sheetName, row, colIndex);
        if (serviceName != null && !serviceName.trim().equals("")) {
            service = new ServiceBean();
            service.setName(serviceName);
            service.setAction(action);
            service.setRow(row.getRowNum());
            service.setServiceLocation(utils.getServiceLocation(sheetName, row.getRowNum(), colIndex));
        }

        return service;
    }

    /**
     * Le as propriedades do servico
     * 
     * @param utils xlsutils
     * @param service servico
     * @param sheetName nome da aba
     * @param row a linha
     * @param action ação a ser executada
     * @param header lista com os dados da linha
     * @throws InputException caso ocorra algum erro.
     */
    private void doReadServiceProperties(XLSUtils utils, ServiceBean service, String sheetName, HSSFRow row, ActionsEnum action,
            List<String> header) throws InputException {
        /* Ler as propriedades desse servico */
        for (int cellPos = XLSFileConstants.RESERVED_COLUMNS; cellPos < row.getLastCellNum(); cellPos++) {
            String headerKey = null;
            if (header.size() > cellPos - XLSFileConstants.RESERVED_COLUMNS) {
                headerKey = header.get(cellPos - XLSFileConstants.RESERVED_COLUMNS);
            } else {
                headerKey = "Field" + (cellPos - XLSFileConstants.RESERVED_COLUMNS);
            }

            String value = utils.getCellContentAsString(sheetName, row, cellPos);

            if ("".equals(value.trim())) {
                value = null;
            }

            service.putParameter(headerKey, value);
        }
    }

}
